package com.simplenix.nicasio.hmaint;

import com.simplenix.nicasio.hmaint.annotations.DescriptionField;
import com.simplenix.nicasio.hmaint.annotations.Digester;
import com.simplenix.nicasio.hmaint.annotations.HMaint;
import com.simplenix.nicasio.hmaint.annotations.Hide;
import com.simplenix.nicasio.hmaint.annotations.Searchable;
import com.simplenix.nicasio.hmaint.annotations.Secret;
import com.simplenix.nicasio.misc.StrUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;

/**
 *
 * @author fronald
 */
public class EntityDefinition {

	public static final String TYPE_STRING = "STRING";

	public static final String TYPE_TEXT = "TEXT";

	public static final String TYPE_LONG = "LONG";

	public static final String TYPE_NUMBER = "NUMBER";

	public static final String TYPE_DATE = "DATE";

	public static final String TYPE_DATE_TIME = "DATE_TIME";

	public static final String TYPE_BOOLEAN = "BOOLEAN";

	private CustomAdd customAdd;

	private CustomUpdate customUpdate;

	private Class entity;

	private List<Annotation> annotations;

	private List<String> order;

	private List<String> searchable;

	private Map<String, String> fieldsTypes;

	private Map<String, Integer> fieldsLenghts;

	private List<String> hiddenOnList;

	private List<String> hiddenOnAdd;

	private List<String> hiddenOnUpdate;

	private List<String> fieldsNames;

	private List<String> secretFields;

	private Map<String, Class> fieldClassType;

	private Map<String, String> digesterField;

	private String descriptionField;

	private String keyField;

	public EntityDefinition(String className) throws HMaintException {
		if (!isValidHMaintEntity(className)) {
			throw new HMaintException("The class is not a valid Entity");
		}
		try {
			this.annotations = new ArrayList();
			this.fieldsTypes = new HashMap();
			this.fieldClassType = new HashMap();
			this.fieldsLenghts = new HashMap();
			this.hiddenOnAdd = new ArrayList();
			this.fieldsNames = new ArrayList();
			this.hiddenOnUpdate = new ArrayList();
			this.hiddenOnList = new ArrayList();
			this.digesterField = new HashMap();
			this.searchable = new ArrayList();
			this.order = new ArrayList();
			this.secretFields = new ArrayList();
			this.entity = Class.forName(className);
			Annotation[] as = this.entity.getAnnotations();
			for (int i = 0; i < as.length; i++) {
				Annotation a = as[i];
				this.annotations.add(a);
			}
			this.load();
		} catch (Exception e) {
			throw new HMaintException(e.getMessage(), e);
		}
	}

	private void load() throws HMaintException {
		boolean isEntity = false;
		boolean isHmaint = false;
		for (Annotation a : this.annotations) {
			if (a.annotationType().getName().equals(Entity.class.getName()) || a.annotationType().getName().equals(org.hibernate.annotations.Entity.class.getName())) {
				isEntity = true;
			} else if (a.annotationType().equals(HMaint.class)) {
				isHmaint = true;
				HMaint h = (HMaint) a;
				this.order = StrUtil.stringToList(StrUtil.nvl(h.order()), ",");
				try {
					this.customUpdate = (CustomUpdate) Class.forName(h.customUpdate()).newInstance();
					this.customAdd = (CustomAdd) Class.forName(h.customAdd()).newInstance();
				} catch (ClassNotFoundException e) {
					throw new HMaintException(e);
				} catch (IllegalAccessException e) {
					throw new HMaintException(e);
				} catch (InstantiationException e) {
					throw new HMaintException(e);
				}
			}
		}
		if (!isEntity || !isHmaint) {
			throw new HMaintException("The class is not a valid Entity");
		}
		Field[] fields = this.entity.getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			Field f = fields[i];
			this.processType(f);
		}
		Method[] methods = this.entity.getDeclaredMethods();
		for (Method method : methods) {
			Annotation[] as = method.getAnnotations();
			for (Annotation a : as) {
				if (a.annotationType().getName().equals(Digester.class.getName())) {
					Digester d = (Digester) a;
					String f = d.forField();
					this.digesterField.put(method.getName(), f);
				}
			}
		}
		if (this.fieldsLenghts.size() != this.fieldsTypes.size()) {
			throw new HMaintException("Error on create a EntityDefinition for class " + this.entity.getName());
		}
		for (String key : this.fieldsLenghts.keySet()) {
			this.fieldsNames.add(key);
		}
		if (this.order.size() > 0) {
			this.orderFields();
		}
	}

	private void orderFields() {
		List<String> newList = new ArrayList();
		for (String s : this.order) {
			if (this.getFieldsNames().contains(s)) {
				newList.add(s);
			}
		}
		for (String f : this.getFieldsNames()) {
			if (!newList.contains(f)) {
				newList.add(f);
			}
		}
		this.fieldsNames = newList;
	}

	private void processType(Field field) throws HMaintException {
		String classType = field.getType().getName();
		String hmaintType = "";
		Integer lenght = 255;
		this.fieldClassType.put(field.getName(), field.getType());
		if (classType.equals(String.class.getName())) {
			hmaintType = TYPE_STRING;
		}
		if (classType.equals(Character.class.getName())) {
			hmaintType = TYPE_STRING;
		} else if (classType.equals(Integer.class.getName()) || classType.equals("int")) {
			hmaintType = TYPE_LONG;
		} else if (classType.equals(Long.class.getName()) || classType.equals("long")) {
			hmaintType = TYPE_LONG;
		} else if (classType.equals(BigInteger.class.getName())) {
			hmaintType = TYPE_LONG;
		} else if (classType.equals(Short.class.getName()) || classType.equals("short")) {
			hmaintType = TYPE_LONG;
		} else if (classType.equals(Float.class.getName()) || classType.equals("float")) {
			hmaintType = TYPE_NUMBER;
		} else if (classType.equals(Double.class.getName()) || classType.equals("double")) {
			hmaintType = TYPE_NUMBER;
		} else if (classType.equals(BigDecimal.class.getName())) {
			hmaintType = TYPE_NUMBER;
		} else if (classType.equals(Boolean.class.getName()) || classType.equals("boolean")) {
			hmaintType = TYPE_BOOLEAN;
		} else if (classType.equals(Date.class.getName())) {
			hmaintType = TYPE_DATE;
		}
		if (!hmaintType.equals("")) {
			try {
				String getter = this.getGetterName(field.getName());
				Method m = this.entity.getDeclaredMethod(getter);
				Annotation[] as = m.getAnnotations();
				for (int i = 0; i < as.length; i++) {
					Annotation a = as[i];
					if (a.annotationType().getName().equals(Lob.class.getName())) {
						if (hmaintType.equals(TYPE_STRING)) {
							hmaintType = TYPE_TEXT;
						}
					}
					if (a.annotationType().getName().equals(Column.class.getName())) {
						Column columnAnnotation = (Column) a;
						lenght = columnAnnotation.length();
						if (lenght == 0) {
							lenght = 255;
						}
					}
					if (a.annotationType().getName().equals(Id.class.getName())) {
						this.keyField = field.getName();
					}
					if (a.annotationType().getName().equals(Searchable.class.getName())) {
						this.searchable.add(field.getName());
					}
					if (a.annotationType().getName().equals(Secret.class.getName())) {
						this.secretFields.add(field.getName());
					}
					if (a.annotationType().getName().equals(DescriptionField.class.getName()) && StrUtil.nvl(this.descriptionField).equals("")) {
						this.descriptionField = field.getName();
					}
					if (a.annotationType().getName().equals(Hide.class.getName())) {
						Hide h = (Hide) a;
						if (StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_ADD) || StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_ADD_AND_UPDATE) || StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_LIST_AND_ADD) || StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_LIST_AND_ADD_AND_UPDATE) || StrUtil.nvl(h.where()).equals("")) {
							this.hiddenOnAdd.add(field.getName());
						}
						if (StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_UPDATE) || StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_ADD_AND_UPDATE) || StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_LIST_AND_ADD_AND_UPDATE) || StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_LIST_AND_UPDATE) || StrUtil.nvl(h.where()).equals("")) {
							this.hiddenOnUpdate.add(field.getName());
						}
						if (StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_LIST) || StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_LIST_AND_ADD) || StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_LIST_AND_UPDATE) || StrUtil.nvl(h.where()).equals(HMaintHide.HIDE_ON_LIST_AND_ADD_AND_UPDATE) || StrUtil.nvl(h.where()).equals("")) {
							this.hiddenOnList.add(field.getName());
						}
					}
				}
			} catch (Exception e) {
				throw new HMaintException(e);
			}
			this.fieldsTypes.put(field.getName(), hmaintType);
			this.fieldsLenghts.put(field.getName(), lenght);
		}
	}

	public String getGetterName(String field) {
		return "get" + StrUtil.capitalize(field);
	}

	public List<String> getDigesters() {
		List<String> rtn = new ArrayList<String>();
		for (String digesterName : this.digesterField.keySet()) {
			rtn.add(digesterName);
		}
		return rtn;
	}

	public String getFieldNameOfDigester(String digesterName) {
		if (this.digesterField.containsKey(digesterName)) {
			return this.digesterField.get(digesterName);
		} else {
			return null;
		}
	}

	public String getDigesterForField(String field) {
		for (String key : this.digesterField.keySet()) {
			String f = this.getFieldNameOfDigester(key);
			if (!StrUtil.nvl(f).equals("") && StrUtil.nvl(f).equals(field)) {
				return key;
			}
		}
		return null;
	}

	public boolean haveDigester(String field) {
		return !StrUtil.nvl(this.getDigesterForField(field)).equals("");
	}

	public List<String> getFieldsNames() {
		return new ArrayList<String>(this.fieldsNames);
	}

	public String getFieldType(String fieldName) {
		return this.fieldsTypes.get(fieldName);
	}

	public Integer getFieldLength(String fieldName) {
		return this.fieldsLenghts.get(fieldName);
	}

	public Object getEntity() throws HMaintException {
		try {
			return this.entity.newInstance();
		} catch (InstantiationException ex) {
			throw new HMaintException(ex);
		} catch (IllegalAccessException ex) {
			throw new HMaintException(ex);
		}
	}

	public Class getEntityClass() throws HMaintException {
		return this.entity;
	}

	public String getKeyFieldName() {
		return StrUtil.nvl(this.keyField);
	}

	public static boolean isValidHMaintEntity(String className) {
		boolean rtn = false;
		try {
			Class c = Class.forName(className);
			Annotation[] a = c.getAnnotations();
			for (int i = 0; i < a.length; i++) {
				Annotation annot = a[i];
				if (annot.annotationType().getName().equals(HMaint.class.getName())) {
					rtn = true;
					break;
				}
			}
		} catch (Exception e) {
			//nada
		}
		return rtn;
	}

	public List<String> getFieldsHiddenOnList() {
		return new ArrayList<String>(this.hiddenOnList);
	}

	public List<String> getFieldsHiddenOnAdd() {
		return new ArrayList<String>(this.hiddenOnAdd);
	}

	public List<String> getFieldsHiddenOnUpdate() {
		return new ArrayList<String>(this.hiddenOnUpdate);
	}

	public List<String> getSearchableFields() {
		return new ArrayList<String>(this.searchable);
	}

	public List<String> getSecretFields() {
		return new ArrayList<String>(this.secretFields);
	}

	public Class getFieldClassType(String field) {
		return this.fieldClassType.get(field);
	}

	public String getDescriptionField() {
		return this.descriptionField;
	}

	/**
	 * @return the customAdd
	 */
	public CustomAdd getCustomAdd() {
		return customAdd;
	}

	/**
	 * @return the customUpdate
	 */
	public CustomUpdate getCustomUpdate() {
		return customUpdate;
	}

}
